using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace ReactiveLang
{
    using Util;

    [ContractClass(typeof(IStatementContract<>))]
    public interface IStatement<out TReturn>
    {
        IDisposable Activate(IStatementObserver<TReturn> observer);
    }

    #region Conract for IStatement<TReturn>
    [ContractClassFor(typeof(IStatement<>))]
    abstract class IStatementContract<TReturn> : IStatement<TReturn>
    {
        public IDisposable Activate(IStatementObserver<TReturn> observer)
        {
            Contract.Requires<ArgumentNullException>(observer != null, "executor");
            Contract.Ensures(Contract.Result<IDisposable>() != null);
            throw new NotImplementedException();
        }
    }

    #endregion

    public static partial class Statement
    {
        #region Create

        class AnonymousStatement<TReturn> : IStatement<TReturn>
        {
            internal Func<IStatementObserver<TReturn>, IDisposable> _activate;

            public IDisposable Activate(IStatementObserver<TReturn> observer)
            {
                return _activate(observer);
            }
        }
        public static IStatement<TReturn> CreateWithDisposable<TReturn>(Func<IStatementObserver<TReturn>, IDisposable> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            return new AnonymousStatement<TReturn> { _activate = activate };
        }
        public static IStatement<TReturn> Create<TReturn>(Func<IStatementObserver<TReturn>, Action> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            return CreateWithDisposable<TReturn>(e => Disposable.Create(activate(e)));
        }

        #endregion

        #region Activate

        public static IDisposable Activate<TReturn>(this IStatement<TReturn> stmt,
            Action<Exception> onException, Func<IExpression<TReturn>, IDisposable> onReturn,
            Func<IDisposable> onBreak, Func<IDisposable> onContinue)
        {
            Contract.Requires<ArgumentNullException>(stmt != null, "stmt");
            Contract.Requires<ArgumentNullException>(onException != null, "onException");
            Contract.Requires<ArgumentNullException>(onReturn != null, "onReturn");
            Contract.Requires<ArgumentNullException>(onBreak != null, "onBreak");
            Contract.Requires<ArgumentNullException>(onContinue != null, "onContinue");
            return stmt.Activate(StatementObserver.Create(onException, onReturn, onBreak, onContinue));
        }
        internal static IDisposable ActivateDerived<TReturn>(this IStatement<TReturn> stmt, IStatementObserver<TReturn> baseObserver,
            Action<Exception> onException = null, Func<IExpression<TReturn>, IDisposable> onReturn = null,
            Func<IDisposable> onBreak = null, Func<IDisposable> onContinue = null)
        {
            Contract.Requires<ArgumentNullException>(stmt != null, "stmt");
            Contract.Requires<ArgumentNullException>(baseObserver != null, "baseExecutor");
            return stmt.Activate(baseObserver.Derive(onException, onReturn, onBreak, onContinue));
        } 
        #endregion

        public static IStatement<TReturn> If<TReturn>(this IExpression<bool> condition, IStatement<TReturn> trueStatement, IStatement<TReturn> falseStatement)
        {
            Contract.Requires<ArgumentNullException>(condition != null, "condition");
            return Create<TReturn>(e =>
            {
                var bodyHandle = Disposable.Empty;
                var conditionHandle = condition.Distinct().ActivateDerived(e, x =>
                {
                    bodyHandle.Dispose();
                    var activeStmt = x ? trueStatement : falseStatement;
                    bodyHandle = (activeStmt != null) ? activeStmt.ActivateDerived(e) : Disposable.Empty;
                });
                return delegate
                {
                    bodyHandle.Dispose();
                    conditionHandle.Dispose();
                };
            });
        }

        public static IStatement<TValue> Return<TValue>(this IExpression<TValue> value)
        {
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return CreateWithDisposable<TValue>(e => e.OnReturn(value));
        }

        public static IStatement<TReturn> Block<TReturn>(params IStatement<TReturn>[] statements)
        {
            Contract.Requires<ArgumentNullException>(statements != null, "statements");
            return CreateWithDisposable<TReturn>(e =>
            {
                var collection = new StatementCollection<TReturn>(e);
                foreach (var stmt in statements)
                    collection.Add(stmt);
                return collection;
            });
        }

        public static IStatement<TReturn> Imperative<TArg, TReturn>(IExpression<TArg> arg, Action<TArg> @do)
        {
            Contract.Requires<ArgumentNullException>(arg != null, "arg");
            Contract.Requires<ArgumentNullException>(@do != null, "@do");
            return CreateWithDisposable<TReturn>(e => arg.ActivateDerived(e, @do));
        }

        public static IStatement<TReturn> AsStatement<TReturn, T>(this IExpression<T> expression)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            return CreateWithDisposable<TReturn>(o => expression.ActivateDerived(o, delegate { }));
        }
    }
}